﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ApplicationSupport;
using System.Reflection;
using System.Data.Common;

namespace Cubes.Data
{
	[global::System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
	public sealed class DataRecordInfoAttribute : Attribute
	{
		public string TableName{ get; set; }
		public string PrimaryKey { get; set; }
	}

	internal static class DataRecordFactory
	{
		public static IEnumerable<T> LoadRecords<T>(string conditions, object[] parameters, string orderBy, int offset, int limit) where T:DataRecordBase
		{
			string tableName = GetTableName<T>();
			string orderByClause = orderBy.Length > 0 ? " ORDER BY " + orderBy : "";
			string whereClause = conditions.Length > 0 ? " WHERE " + conditions : "";

			string sqlCommand = String.Format("SELECT * FROM {0}{1}{2}", tableName, whereClause, orderByClause);

			List<T> result = new List<T>();

			using (DbDataReader reader = DataTools.Default.GetDataReader(sqlCommand, parameters))
			{
				while (reader.Read())
				{
					ConstructorInfo ctor = typeof(T).GetConstructor(new Type[0]);
					var dataRecord = (T)ctor.Invoke(new object[0]);
					for (int i = 0; i < reader.FieldCount; i++)
					{
						dataRecord.WriteField(reader.GetName(i), reader[i]);
					}
					result.Add(dataRecord);
				}
			}
			return result;
		}
		public static string GetTableName<T>()
		{
			string name = GetCustomAttribute<T>(typeof(DataRecordInfoAttribute), "TableName");
			if (name.Length > 0)
			{
				return name;
			}
			throw new InvalidDataRecordException("No DataRecordInfo attribute was specified.");
		}
		public static string GetPrimaryKey<T>()
		{
			string key = GetCustomAttribute<T>(typeof(DataRecordInfoAttribute), "PrimaryKey");
			return key.Length > 0 ? key : "ID";
		}
		private static string GetCustomAttribute<T>(Type attributeType, string propertyName)
		{
			Type t = typeof(T);
			var att = t.GetCustomAttributes(attributeType, false).FirstOrDefault();
			if(att != null)
			{
				return attributeType.GetProperty(propertyName).GetValue(att, null).ToString();
			}
			return "";
		}
	}

	public abstract class DataRecordBase
	{
		public abstract object ReadField(string fieldName);
		public abstract void WriteField(string fieldName, object value);
	}
	public abstract class DataRecord<TObject, TPrimaryKey>:DataRecordBase where TObject:DataRecordBase, new()
	{
		#region Constructors
		protected DataRecord() { }
		public DataRecord(TPrimaryKey id) { this.WriteField("", id); }
		#endregion

		#region Internal Fields
		private Hashtable _fields = null;
		#endregion

		#region Field Readers

		/// <summary>
		/// Retrives a Boolean from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected bool ReadBoolean(string field)
		{
			return SafeConvert.ToBoolean(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a Byte from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected byte ReadByte(string field)
		{
			return SafeConvert.ToByte(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a Int16 from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected short ReadInt16(string field)
		{
			return SafeConvert.ToInt16(this.ReadField(field));
		}

		/// <summary> 
		/// Retrives a Int32 from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected int ReadInt32(string field)
		{
			return SafeConvert.ToInt32(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a Int64 from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected long ReadInt64(string field)
		{
			return SafeConvert.ToInt64(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a Float from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected float ReadFloat(string field)
		{
			return SafeConvert.ToFloat(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a Double from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected double ReadDouble(string field)
		{
			return SafeConvert.ToDouble(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a Decimal from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected decimal ReadDecimal(string field)
		{
			return SafeConvert.ToDecimal(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a DateTime from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected DateTime ReadDateTime(string field)
		{
			return SafeConvert.ToDateTime(this.ReadField(field));
		}

		/// <summary>
		/// Retrives a String from a field
		/// </summary>
		/// <param name="field">The field name</param>
		protected string ReadString(string field)
		{
			return SafeConvert.ToString(this.ReadField(field)).ToString();
		}

		/// <summary>
		/// Retrives a Object from a field
		/// </summary>
		/// <param name="field">The field name</param>
		public override object ReadField(string field)
		{
			if (this.Fields.Count == 0)
			{
				throw new InvalidDataRecordException("The " + this.TableName + " [" + this.ID + "] is not in database.");
			}

			object fieldValue = this.Fields[field.ToLower()];

			if (fieldValue == null)
			{
				throw new InvalidDataRecordException("The " + this.TableName + " [" + this.ID + "] has not a \"" + field + "\" field.");
			}

			return fieldValue;
		}

		#endregion

		#region Field Storage

		private Hashtable Fields
		{
			get
			{
				if (this._fields == null)
				{
					this._fields = new Hashtable();
				}
				return this._fields;
			}
		}
		
		#endregion

		#region Field Writer

		public override void WriteField(string fieldName, object value)
		{
			this.Fields.Add(fieldName, value);
		}

		#endregion

		#region Finders
		public static IEnumerable<TObject> All()
		{
			return DataRecordFactory.LoadRecords<TObject>("", new object[0], "", 0, 0);
		}

		public static IEnumerable<TObject> Find(string conditions)
		{
			//throw new NotImplementedException();
			return DataRecordFactory.LoadRecords<TObject>(conditions, new object[0], "", 0, 0);
		}

		public static IEnumerable<TObject> Find(string conditions, int offset, int limit)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(string conditions, int offset, int limit, string orderBy)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(string conditions, object[] parameters)
		{
			return DataRecordFactory.LoadRecords<TObject>(conditions, parameters, "", 0, 0);
		}

		public static IEnumerable<TObject> Find(string conditions, object[] parameters, int offset, int limit)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(string conditions, object[] parameters, int offset, int limit, string orderBy)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(object conditions)
		{
			throw new NotImplementedException();
			//return DataRecordFactory<TObject>.LoadRecords(conditions, new object[0], "", 0, 0);
		}

		public static IEnumerable<TObject> Find(object conditions, int offset, int limit)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(object conditions, int offset, int limit, string orderBy)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(int offset, int limit)
		{
			throw new NotImplementedException();
		}

		public static IEnumerable<TObject> Find(int offset, int limit, string orderBy)
		{
			throw new NotImplementedException();
		}

		public static TObject First()
		{
			throw new NotImplementedException();
		}
		#endregion

		#region Properties

		public bool IsNewRecord { get; internal set; }
		
		public TPrimaryKey ID { get { return (TPrimaryKey)this.ReadField(this.PrimaryKeyFieldName); } }

		public DateTime CreatedAt
		{
			get { return this.ReadDateTime("CREATED_AT"); }
			set { this.WriteField("CREATED_AT", value); }
		}

		public DateTime UpdatedAt
		{
			get { return this.ReadDateTime("UPDATED_AT"); }
			set { this.WriteField("UPDATED_AT", value); }
		}
		
		#endregion

		#region Type meta-informartion
		protected string TableName
		{
			get { return DataRecordFactory.GetTableName<TObject>(); }
		}

		protected string PrimaryKeyFieldName
		{
			get { return DataRecordFactory.GetPrimaryKey<TObject>(); }
		}
		#endregion
	}
}
